/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.collections.maps;

import static com.softwarecraftsmen.functors.Boolean.*;
import com.softwarecraftsmen.collections.Key;
import com.softwarecraftsmen.collections.tuples.Pair;
import com.softwarecraftsmen.functors.*;
import com.softwarecraftsmen.functors.Boolean;
import org.jetbrains.annotations.NotNull;

import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class LinkedHashMutableMap<K extends Key, V> implements MutableMap<K, V>
{
	public final LinkedHashMap<LegacyCollectionSupport<K>, V> internalRepresentation;

	public LinkedHashMutableMap()
	{
		internalRepresentation = new LinkedHashMap<LegacyCollectionSupport<K>, V>();
	}

	private LinkedHashMutableMap(final @NotNull LinkedHashMap<LegacyCollectionSupport<K>, V> internalRepresentation)
	{
		this.internalRepresentation = internalRepresentation;
	}

	@NotNull
	public Boolean isEmpty()
	{
		return fromLegacy(size() == 0);
	}

	@NotNull
	public com.softwarecraftsmen.functors.Boolean hasElements()
	{
		return isEmpty().not();
	}

	@NotNull
	public V valueAt(final @NotNull K key)
	{
		return doesNotContain(key).choiceReturn
		(
			new NullaryReturningFunctor<V>() {@NotNull public V closure()
			{
				throw new KeyDoesNotExistInMapException(key);
			}},
			new NullaryReturningFunctor<V>() {@NotNull public V closure()
			{
				return internalRepresentation.get(legacyCollectionSupport(key));
			}}
		);
	}

	public int size()
	{
		return internalRepresentation.size();
	}

	@NotNull
	public Boolean contains(final @NotNull K key)
	{
		return fromLegacy(internalRepresentation.containsKey(legacyCollectionSupport(key)));
	}

	@NotNull
	public Boolean doesNotContain(final @NotNull K key)
	{
		return contains(key).not();
	}

	@SuppressWarnings({"unchecked"})
	@NotNull
	public Map<K, V> defensiveCopy()
	{
		return new LinkedHashMutableMap<K, V>((LinkedHashMap<LegacyCollectionSupport<K>, V>)internalRepresentation.clone());
	}

	@NotNull
	public Boolean isOrdered()
	{
		return True;
	}

	@NotNull
	public Boolean isSorted()
	{
		return False;
	}

	@NotNull
	public HashSet<Pair<K, V>> toLegacyHashSet()
	{
		return (HashSet<Pair<K, V>>) addToSet(new HashSet<Pair<K, V>>());
	}

	@NotNull
	public LinkedHashSet<Pair<K, V>> toLegacyLinkedHashSet()
	{
		return (LinkedHashSet<Pair<K, V>>) addToSet(new LinkedHashSet<Pair<K, V>>());
	}

	@NotNull
	public TreeSet<Pair<K, V>> toLegacyTreeSet()
	{
		return (TreeSet<Pair<K, V>>) addToSet(new TreeSet<Pair<K, V>>());
	}

	@NotNull
	public TreeSet<Pair<K, V>> toLegacyTreeSet(@NotNull final Comparator<? super Pair<K, V>> comparator)
	{
		return (TreeSet<Pair<K, V>>) addToSet(new TreeSet<Pair<K, V>>(comparator));
	}

	private Set<Pair<K, V>> addToSet(final Set<Pair<K, V>> set)
	{
		for (Entry<LegacyCollectionSupport<K>, V> entry : internalRepresentation.entrySet())
		{
			final K key = entry.getKey().key;
			set.add(Pair.<K, V>pair(key, entry.getValue()));
		}
		return set;
	}

	@NotNull
	public Boolean isMutable()
	{
		return True;
	}


	public void putIfNotPresent(final @NotNull K key, final @NotNull V value)
	{
		doesNotContain(key).ifTrue      (new NullaryFunctor(){public void closure()
		{
			putOrReplace(key, value);
		}
		});
	}

	public void putIfNotPresent(final @NotNull Pair<K, V> entry)
	{
		putIfNotPresent(entry.a, entry.b);
	}

	public void replaceIfPresent(final @NotNull K key, final @NotNull V value)
	{
		contains(key).ifTrue            (new NullaryFunctor(){public void closure()
		{
			putOrReplace(key, value);
		}
		});
	}

	public void replaceIfPresent(final @NotNull Pair<K, V> entry)
	{
		replaceIfPresent(entry.a, entry.b);
	}

	public void putOrReplace(final @NotNull K key, final @NotNull V value)
	{
		internalRepresentation.put(legacyCollectionSupport(key), value);
	}

	public void putOrReplace(final @NotNull Pair<K, V> entry)
	{
		putOrReplace(entry.a, entry.b);
	}

	@NotNull
	public HashMap<K, V> toLegacyHashMap()
	{
		return (HashMap<K, V>) populate(new HashMap<K, V>());
	}

	@NotNull
	public LinkedHashMap<K, V> toLegacyLinkedHashMap()
	{
		return (LinkedHashMap<K, V>) populate(new LinkedHashMap<K, V>());
	}

	@NotNull
	public TreeMap<K, V> toLegacyTreeMap()
	{
		return (TreeMap<K, V>) populate(new TreeMap<K, V>());
	}

	@NotNull
	public TreeMap<K, V> toLegacyTreeMap(@NotNull final Comparator<? super K> comparator)
	{
		return (TreeMap<K, V>) populate(new TreeMap<K, V>(comparator));
	}

	private java.util.Map<K, V> populate(final @NotNull java.util.Map<K, V> map)
	{
		for (Entry<LegacyCollectionSupport<K>, V> entry : internalRepresentation.entrySet())
		{
			map.put(entry.getKey().key, entry.getValue());
		}
		return map;
	}

	private static <K extends Key> LegacyCollectionSupport<K> legacyCollectionSupport(final K key)
	{
		return new LegacyCollectionSupport<K>(key);
	}

	private static class LegacyCollectionSupport<K extends Key>
	{
		private final K key;

		public LegacyCollectionSupport(final @NotNull K key)
		{
			this.key = key;
		}

		@SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"})
		public boolean equals(final @NotNull Object o)
		{
			return key.isEqualTo(((LegacyCollectionSupport)o).key).toLegacyBoolean();
		}

		public int hashCode()
		{
			return key.computeHashCode();
		}
	}
}
